<script setup lang="ts">
import { LockOutlined, UserOutlined } from '@ant-design/icons-vue';
import { onMounted, reactive, ref } from 'vue';

import AuthService from '../authentication/auth-service';
import { FormItemValidationResultMapping } from '../shared/models/form-item-helper';
import FormItemHelper from '../shared/models/form-item-helper';
import FormTextFieldValidator, { FormTextFieldValidatorArguments } from '../shared/services/form-text-field-validator';
import UserService from './user-service';

defineProps<{
  id?: string | null,
}>();

const emit = defineEmits<{
  (e: 'cancel'): void,
  (e: 'ok'): void,
}>();

const usernameInput = ref(null);
const form = ref(null);
const formName = 'form';

const validationResultMappingDefaultValue: FormItemValidationResultMapping = {
  username: {
    name: 'username',
    label: '用户名',
  },
  password: {
    name: 'password',
    label: '密码',
  },
  confirmPassword: {
    name: 'confirmPassword',
    label: '确认密码',
  },
};

const validationResultMapping = reactive<FormItemValidationResultMapping>(validationResultMappingDefaultValue);

const formState = reactive({
  username: '',
  password: '',
  confirmPassword: '',
});

onMounted(() => (usernameInput.value! as any).focus());

/**
 * Performs validation on form item. It includes password & confirmPassword match check. 
 */
function validateFormItem(key: string, value: string, validatorArguments?: FormTextFieldValidatorArguments): void {
  // Updates form data for validating confirmPassword.
  validationResultMapping[key].value = value;

  const validator = new FormTextFieldValidator(validatorArguments);

  if (key === validationResultMapping.password.name) {
    validator.args.label = validationResultMapping.password.label!;
    validator.args.value = validationResultMapping.password.value;
    validator.args.label2 = validationResultMapping.confirmPassword.label!;
    validator.args.value2 = validationResultMapping.confirmPassword.value;
  } else if (key === validationResultMapping.confirmPassword.name) {
    validator.args.label = validationResultMapping.confirmPassword.label!;
    validator.args.value = validationResultMapping.confirmPassword.value;
    validator.args.label2 = validationResultMapping.password.label!;
    validator.args.value2 = validationResultMapping.password.value;
  } else {
    validator.args.label = validationResultMapping[key].label!;
    validator.args.value = value;
  }

  // Merges object changes.
  Object.assign(validationResultMapping[key], FormItemHelper.validate(validator));

  // If password & confirmPassword meet the validation rules and their values are identical then we need to set both status to success.
  if (new FormTextFieldValidator({
    ...validationResultMapping.password.validatorArguments,
    label: validationResultMapping.password.label,
    value: validationResultMapping.password.value,
  }).validate() === null &&
    new FormTextFieldValidator({
      ...validationResultMapping.confirmPassword.validatorArguments,
      label: validationResultMapping.confirmPassword.label,
      value: validationResultMapping.confirmPassword.value,
    }).validate() === null &&
    validationResultMapping.password.value === validationResultMapping.confirmPassword.value
  ) {
    validationResultMapping.password = Object.assign(validationResultMapping.password, FormItemHelper.getSuccessValidationResult());
    validationResultMapping.confirmPassword = Object.assign(validationResultMapping.confirmPassword, FormItemHelper.getSuccessValidationResult());
  }
}

/**
 * Handles input value change for validation purpose.
 */
function handleChange(e: any): void {
  // The id of input under form-item is auto-assigned. It will be started with form name. That why we need to remove it.
  const key = e.target.id.replace(`${formName}_`, '');
  const value = e.target.value;

  validateFormItem(key, value, validationResultMapping[key].validatorArguments);
}

/**
 * When form submitted.
 */
async function onFinish(values: any): Promise<void> {
  for (const key in validationResultMapping) {
    validateFormItem(key, values[validationResultMapping[key].name!], validationResultMapping[key].validatorArguments);
  }

  if (FormItemHelper.isFormValidationSuccessful(validationResultMapping)) {
    const usernameExists = await AuthService.checkOnUsername(values.username);

    if (!usernameExists) {
      await UserService.add({
        username: values.username,
        fullName: values.username,
        password: values.password,
      });

      emit('ok');

      // Resets form values.
      (form.value! as any).resetFields();
    } else {
      validationResultMapping.username = Object.assign(validationResultMapping.username, FormItemHelper.getErrorValidationResult('该用户名已被占用，请重新输入。'));
    }
  }
};

function onCancel(): void {
  // Closes model.
  emit('cancel');

  // Resets validation status.
  for (const key in validationResultMapping) {
    validationResultMapping[key].validateStatus = null;
    validationResultMapping[key].errorMsg = null;
  }

  // Resets form values.
  (form.value! as any).resetFields();
}

function validateForm(values: any): void {
  for (const key in validationResultMapping) {
    const validator = new FormTextFieldValidator(validationResultMapping[key].validatorArguments);
    validator.args.label = validationResultMapping[key].label!;
    validator.args.value = values[validationResultMapping[key].name!];

    // Merges two objects.  
    Object.assign(validationResultMapping[key], FormItemHelper.validate(validator));
  }
}
</script>

<template>
  <a-form ref="form" :name="formName" :model="formState" autocomplete="off" @finish="onFinish">
    <a-form-item :name="validationResultMapping.username.name"
      :validateStatus="validationResultMapping.username.validateStatus"
      :help="validationResultMapping.username.errorMsg">
      <a-input ref="usernameInput" v-model:value="formState.username"
        :placeholder="validationResultMapping.username.label" @change="handleChange">
        <template #prefix>
          <UserOutlined class="site-form-item-icon" />
        </template>
      </a-input>
    </a-form-item>
    <a-form-item :name="validationResultMapping.password.name"
      :validateStatus="validationResultMapping.password.validateStatus"
      :help="validationResultMapping.password.errorMsg">
      <a-input-password v-model:value="formState.password" :placeholder="validationResultMapping.password.label"
        @change="handleChange">
        <template #prefix>
          <LockOutlined class="site-form-item-icon" />
        </template>
      </a-input-password>
    </a-form-item>
    <a-form-item :name="validationResultMapping.confirmPassword.name"
      :validateStatus="validationResultMapping.confirmPassword.validateStatus"
      :help="validationResultMapping.confirmPassword.errorMsg">
      <a-input-password v-model:value="formState.confirmPassword"
        :placeholder="validationResultMapping.confirmPassword.label" @change="handleChange">
        <template #prefix>
          <LockOutlined class="site-form-item-icon" />
        </template>
      </a-input-password>
    </a-form-item>
    <a-form-item class="button-container">
      <a-space>
        <a-button @click="onCancel">取消</a-button>
        <a-button type="primary" html-type="submit">保存</a-button>
      </a-space>
    </a-form-item>
  </a-form>
</template>

<style scoped>
.button-container {
  display: flex;
  flex-direction: row;
  justify-content: flex-end;
}
</style>